5 Control flow

Published

April 12, 2025

Modified

April 12, 2025

Introduction

R 中有两类主要的控制流类型工具:选择(if)和循环(for)。选择包含ifswitch()等声明;循环包含forwhile等声明。这里假定你已经学会了它们的基础用法,本章主要介绍一些技术细节和鲜为人知的高级用法。

Outline

  • 5.2节:介绍ififelse()switch()函数。
  • 5.3节:介绍forwhilerepeat等声明。

Choices

下面是if-else语句的基本使用格式:

if (condition) true_action

if (condition) true_action else false_action

if (condition1) {
  true_action1
} else if (condition2) {
  true_action2
} else {
  false_action
}

if语句也可以进行赋值操作;在实际书写代码时,建议只有在if-else语句可以写为一行时,才使用赋值操作。

x1 <- if (TRUE) 1 else 2
x2 <- if (FALSE) 1 else 2

c(x1, x2)
#> [1] 1 2

当if-else语句只有if声明时,如果条件不满足,则返回NULL。函数c()paste()会自动去除返回值中的NULL值。

greet <- function(name, birthday = FALSE) {
  paste0(
    "Hi ", name,
    if (birthday) " and HAPPY BIRTHDAY"
  )
}
greet("Maria", FALSE)
#> [1] "Hi Maria"
greet("Jaime", TRUE)
#> [1] "Hi Jaime and HAPPY BIRTHDAY"

Invalid inputs

需要注意的是,if声明中的条件返回值只能是长度为1的布尔值。如果长度大于1,在R 4.0版本前会选择第一个值,但在R 4.0版本后会报错。其他类型的输入也会报错。

if ("x") 1
#> Error in if ("x") 1: argument is not interpretable as logical
if (logical()) 1
#> Error in if (logical()) 1: argument is of length zero
if (NA) 1
#> Error in if (NA) 1: missing value where TRUE/FALSE needed
if (c(TRUE, FALSE)) 1
#> Error in if (c(TRUE, FALSE)) 1: the condition has length > 1

Vectorised if

if-else 语句只能判断一次,假如你想要判断很多次,可以使用ifelse()函数。该函数接受三个参数:条件,返回值,其他值。如果条件为TRUE,返回值作为结果,否则返回其他值。条件处可以是向量,返回的也是向量。(可以理解for循环if-else语句)

x <- c(1:10, NA, 12)
ifelse(x %% 5 == 0, "XXX", as.character(x))
#>  [1] "1"   "2"   "3"   "4"   "XXX" "6"   "7"   "8"   "9"   "XXX" NA    "12"

ifelse(x %% 2 == 0, "even", "odd")
#>  [1] "odd"  "even" "odd"  "even" "odd"  "even" "odd"  "even" "odd"  "even"
#> [11] NA     "even"
Tip

建议只有在yesno条件的返回值类型一致时,再使用ifelse()函数。如果不同,因为c()是atomic向量,会强制进行类型转换。函数要求得条件如果不是布尔值,则会进行类型转换as.logical(),如果转换结果仍不是布尔值,则会返回转换后的值。

dplyr包提供了等价函数case_when(),使用方法如下:

dplyr::case_when(
  x %% 35 == 0 ~ "fizz buzz",
  x %% 5 == 0 ~ "fizz",
  x %% 7 == 0 ~ "buzz",
  is.na(x) ~ "???",
  TRUE ~ as.character(x)
)
#>  [1] "1"    "2"    "3"    "4"    "fizz" "6"    "buzz" "8"    "9"    "fizz"
#> [11] "???"  "12"

switch() statement

switch()语句是对if-else语句的压缩,例如你可以将下面的if-else语句:

x_option <- function(x) {
  if (x == "a") {
    "option 1"
  } else if (x == "b") {
    "option 2"
  } else if (x == "c") {
    "option 3"
  } else {
    stop("Invalid `x` value")
  }
}

简化为switch()语句:

x_option <- function(x) {
  switch(x,
    a = "option 1",
    b = "option 2",
    c = "option 3",
    stop("Invalid `x` value")
  )
}

再判断条件的末尾添加错误信息,可以提高代码的可读性,因为当不满足匹配条件时,switch()语句返回NULL

(switch("c",
  a = 1,
  b = 2
))
#> NULL

如果不同的输入条件返回值相同,可以省略返回值,switch()会自动向下匹配,例如:

legs <- function(x) {
  switch(x,
    cow = ,
    horse = ,
    dog = 4,
    human = ,
    chicken = 2,
    plant = 0,
    stop("Unknown input")
  )
}
legs("cow")
#> [1] 4
legs("dog")
#> [1] 4
Tip

switch()的输入可以是数值、字符串,但建议只使用字符串。

Exercises

Loops

for 循环的基本格式如下:

for (item in vector) perform_action

有两种中断循环的方法:breaknextbreak用于跳出整个循环,next用于跳出当前循环,继续下一个循环。

for (i in 1:10) {
  if (i < 3) {
    next
  }

  print(i)

  if (i >= 5) {
    break
  }
}
#> [1] 3
#> [1] 4
#> [1] 5
Note

要注意在环境变量中不要有与item名重复的变量。for循环会赋值给item变量,这样会导致item变量的值变化。

i <- 100
for (i in 1:3) {}
i
#> [1] 3

Common pitfalls

在使用for循环时,有三个常见的易错陷阱:

  • 进行赋值操作前,没有定义容纳结果的变量。
  • 使用1:length(x)作为索引,而不是seq_along(x)
  • 直接索引S3对象。

如果没有事先定义容器,会导致for循环十分缓慢。可以使用vector()函数,定义容器类型:

means <- c(1, 50, 20)
out <- vector("list", length(means))
for (i in 1:length(means)) {
  out[[i]] <- rnorm(10, means[[i]])
}

1:length(x)在x的长度为0时,会报错。因为:对升序和降序都兼容,使用seq_along()函数可以变相的解决该问题。seq_along()函数返回一个长度与x相同的等差向量。

x <- c(1, 2, 3, 1,2,3)
y <- numeric(0)

1:length(x)
#> [1] 1 2 3 4 5 6
seq_along(x)
#> [1] 1 2 3 4 5 6

1:length(y) # 在for循环中报错
#> [1] 1 0
seq_along(y)
#> integer(0)
means <- c()
out <- vector("list", length(means))
for (i in 1:length(means)) {
  out[[i]] <- rnorm(10, means[[i]])
}
#> Error in rnorm(10, means[[i]]): invalid arguments

out <- vector("list", length(means))
for (i in seq_along(means)) {
  out[[i]] <- rnorm(10, means[[i]])
}

直接迭代S3对象时,for循环会丢掉S3对象的属性:

xs <- as.Date(c("2020-01-01", "2010-01-01"))
for (x in xs) {
  print(x)
}
#> [1] 18262
#> [1] 14610

for (i in seq_along(xs)) {
  print(xs[[i]])
}
#> [1] "2020-01-01"
#> [1] "2010-01-01"

Exercises

  1. 一定要避免使用1:length(x),下面的例子,不会报错,但是返回结果不对。
x <- numeric()
out <- vector("list", length(x))
for (i in 1:length(x)) {
  out[i] <- x[i]^2
}
out
#> [[1]]
#> [1] NA

x <- numeric()
out <- vector("list", length(x))
for (i in seq_along(x)) {
  out[i] <- x[i]^2
}
out
#> list()
  1. R的for循环只评估一次输入,即使for循环中对评估进行了更新,也不会改变,避免了无限循环的可能。
xs <- c(1, 2, 3)
for (x in xs) {
  xs <- c(xs, x * 2)
}
xs
#> [1] 1 2 3 2 4 6
  1. R的for循环对于item的更新发生在每次迭代开始前,for循环中对item进行的更新无效。
for (i in 1:3) {
  i <- i * 2
  print(i)
}
#> [1] 2
#> [1] 4
#> [1] 6
Back to top